/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;

namespace Microsoft.VSPowerToys.PackInstallerDataModel
{

    /// <summary>
    /// This class is a container for the list of Packs and Tools.  This also gives us a good place to 
    /// implement a method for loading the internal data structures from the data set generated by the schema file.
    /// </summary>
    
    public class PackLibrary
    {
        delegate void SortMethodGrouper(Collection<ToolItem> tools, SortedList<string, PackItem> groupedItems);

        private Dictionary<ToolItem.SortByField, SortMethodGrouper> sortMethodGroupers;

        /// <summary>
        /// Collection of tools regardless of their default packs.
        /// </summary>
        private Collection<ToolItem> toolCollection;

        /// <summary>
        /// Gets the collection of tools available regardless of their pack
        /// </summary>
        public Collection<ToolItem> Tools
        {
            get { return this.toolCollection; }
        }

        /// <summary>
        /// Constructor, creates an empty list of packs to start out with.
        /// </summary>
        public PackLibrary()
        {
            sortMethodGroupers = new Dictionary<ToolItem.SortByField, SortMethodGrouper>();
            sortMethodGroupers.Add(ToolItem.SortByField.CurrentlyInstalled, GrouperMethods.CurrentlyInstalledGrouper);
            sortMethodGroupers.Add(ToolItem.SortByField.Author, GrouperMethods.AuthorGrouper);
            sortMethodGroupers.Add(ToolItem.SortByField.DefaultGroup, GrouperMethods.DefaultGrouper);
            sortMethodGroupers.Add(ToolItem.SortByField.MicrosoftProvidedTool, GrouperMethods.MicrosoftProvidedToolGrouper);
            sortMethodGroupers.Add(ToolItem.SortByField.ReleaseDate, GrouperMethods.ReleaseDateGrouper);
            sortMethodGroupers.Add(ToolItem.SortByField.ToolName, GrouperMethods.ToolNameGrouper);
        }

        /// <summary>
        /// Checks and returns the formatted url
        /// </summary>
        /// <param name="url">URL to verify</param>
        /// <returns>null if url is not valid, formatted url otherwise</returns>
        private static string VerifyUrl(string url) 
        {
            string result = null;
            if (!String.IsNullOrEmpty(url)) 
            {
                url = HttpUtility.HtmlDecode(url);
                if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
                {
                    result = url;
                }
            }
            return result;
        }

        /// <summary>
        /// Loads pack data from an atom feed
        /// </summary>
        /// <param name="feedUri"></param>
        public void LoadPackDataFromAtomFeed(string feedUri)
        {
            AtomFeed list = null;
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.Schemas.Add("http://www.w3.org/2005/Atom", new XmlTextReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.VSPowerToys.PackInstallerDataModel.AtomFeed.xsd")));
            settings.ValidationFlags |= System.Xml.Schema.XmlSchemaValidationFlags.AllowXmlAttributes;
            settings.ValidationType = ValidationType.Schema;
            XmlReader reader = XmlReader.Create(feedUri, settings);
            XmlSerializer s = new XmlSerializer(typeof(AtomFeed));
            list = s.Deserialize(reader) as AtomFeed;
            if (list == null) throw new XmlException();

            // Initialize the pack list so we get rid of any old data.
            this.toolCollection = new Collection<ToolItem>();
            
            ToolItem newTool = null;
            foreach (PackInstallerToolItem item in list.Items)
            {
                newTool = new ToolItem();

                // Read this first so we can override it if we need to.
                newTool.HideThisTool = item.HideTool;
                if (item.Authors.Count == 0) throw new XmlException("Author is missing for a tool entry");
                newTool.Author = HttpUtility.HtmlDecode(item.Authors[0].Name);
                newTool.DetailedDescription = HttpUtility.HtmlDecode(item.Content.Text);
                string homePageUrl = VerifyUrl(item.HomePageUrl);
                if (homePageUrl != null)
                {
                    newTool.HomepageUrl = new Uri(homePageUrl);
                    newTool.HideThisTool |= newTool.HomepageUrl.IsUnc;
                }
                else
                {
                    newTool.HideThisTool = true;
                }
                newTool.ProductGuid = item.ProductGuid.ToString();
                newTool.ReleaseDate = item.LastUpdate.ToString();
                newTool.ToolName = HttpUtility.HtmlDecode(item.Title);
                newTool.VersionString = item.Version;
                newTool.SetupFileName = item.SetupFileName;
                string setupUrl = VerifyUrl(item.SetupUrl);
                if (setupUrl != null)
                {
                    newTool.SetupUrl = new Uri(setupUrl);
                    newTool.HideThisTool |= newTool.SetupUrl.IsUnc;
                }
                else
                {
                    newTool.HideThisTool = true;
                }
                newTool.SetupFileHash = item.SetupFileHash;
                newTool.MicrosoftProvidedTool = item.IsMicrosoftProvided;
                // To avoid conflicts with common filenames for EULAs we append the toolname onto the front of it so we make it more unique.
                newTool.LicenseAgreementFileName = newTool.ToolName + " " + item.LicenseFileName;
                string licenseUrl = VerifyUrl(item.LicenseUrl);
                if (licenseUrl != null)
                {
                    newTool.LicenseAgreementUrl = new Uri(licenseUrl);
                    newTool.HideThisTool |= newTool.LicenseAgreementUrl.IsUnc;
                }
                else
                {
                    newTool.HideThisTool = true;
                }

                foreach (AtomCategory category in item.Categories)
                {
                    newTool.Tags.Add(category.Term);
                }
                newTool.Tags.Add(newTool.Author);

                newTool.DefaultTag = item.DefaultPackName;
                this.toolCollection.Add(newTool);
            }
        }
        
        private IComparer<string> GetCurrentComparer(ToolItem.SortByField sortField, SortOrder order)
        {
            IComparer<string> currentComparer = null;

            switch (sortField)
            {
                case ToolItem.SortByField.ReleaseDate:
                {
                    currentComparer = new ReleaseDatePackItemComparer(sortField, order);
                    break;
                }
                case ToolItem.SortByField.DefaultGroup:
                case ToolItem.SortByField.ToolName:
                case ToolItem.SortByField.Author:
                case ToolItem.SortByField.CurrentlyInstalled:
                case ToolItem.SortByField.MicrosoftProvidedTool:
                default:
                {
                    currentComparer = new DefaultPackItemComparer(sortField, order);
                    break;
                }
            }

            return (currentComparer);
        }

        /// <summary>
        /// Gets a new set of PackItem groups that are defined and sorted based on the sort type and order
        /// </summary>
        /// <param name="sortField">Sort field to use (if None default ordering will be used)</param>
        /// <param name="order">Sort order (default is Ascending)</param>
        public SortedList<string, PackItem> GetGroupsOfTools(ToolItem.SortByField sortField, SortOrder order)
        {
            SortedList<string, PackItem> groupedItems = null;
            
            if (sortMethodGroupers != null)
            {
                groupedItems = new SortedList<string, PackItem>(GetCurrentComparer(sortField, order));

                sortMethodGroupers[sortField].Invoke(this.toolCollection, groupedItems);
                foreach (KeyValuePair<string, PackItem> pack in groupedItems)
                {
                    pack.Value.Tools.Sort(new ToolItemComparer(sortField, order));
                }
            }

            return (groupedItems);
        }       
    }
}
